/* command.c, Ait, BSD 3-Clause, Kevin Bloom, 2023,
   Derived from: Atto January 2017
   Derived from: AnthonyEditor January 93
*/

#include "header.h"
#include "termbox.h"
#include "util.h"

void quit() { done = 1; }
void up()
{
  curbp->b_point = lncolumn(curbp, upup(curbp, curwp, curbp->b_point),curbp->b_pcol - curwp->w_left);
}
void down()
{
  curbp->b_point = lncolumn(curbp, dndn(curbp, curwp, curbp->b_point),curbp->b_pcol - curwp->w_left);
}
void lnbegin()
{
  char_t *p;
  p = ptr(curbp, curbp->b_point);
  while(*(p = ptr(curbp, curbp->b_point-1)) != '\n' && p > curbp->b_buf)
    --curbp->b_point;
  if(curbp->b_point != 0 && p == curbp->b_buf)
    --curbp->b_point;
  curbp->b_pcol = 0;
}
void version() { msg(VERSION); }
void top() { curbp->b_point = 0; }
void bottom()
{
  curbp->b_point = pos(curbp, curbp->b_ebuf);
  if (curbp->b_epage < pos(curbp, curbp->b_ebuf))
    curbp->b_reframe = 1;
}
void block() { curbp->b_mark = curbp->b_point; }
void copy() { copy_cut(FALSE, TRUE, FALSE); }
void cut() { copy_cut(TRUE, TRUE, FALSE); }
void resize_terminal()
{
  LINES = tb_height();
  COLS = tb_width();
  MSGLINE = LINES-1;
  one_window(curwp);
}

void print_to_msgline(const char *msg)
{
  printf_tb(0, MSGLINE, TB_DEFAULT, TB_DEFAULT, msg);
  tb_set_cursor(strlen(msg), MSGLINE);
}

void quit_ask()
{
  if (modified_buffers() > 0) {
    const char *msg = "Modified buffers exist; really exit (y/N) ?";
    print_to_msgline(msg);
    clrtoeol(msg, MSGLINE);
    if (!yesno(FALSE)) {
      clrtoeol("", MSGLINE);
      return;
    }
  }
  quit();
}


void redraw()
{
  window_t *wp;
  for (wp=wheadp; wp != NULL; wp = wp->w_next)
    wp->w_update = TRUE;
  update_display();
}

void left()
{
  int n = prev_utf8_char_size();
  while (0 < curbp->b_point && n-- > 0)
    --curbp->b_point;
}

void right()
{
  int n = utf8_size(*ptr(curbp,curbp->b_point));
  while ((curbp->b_point < pos(curbp, curbp->b_ebuf)) && n-- > 0)
    ++curbp->b_point;
}

/* work out number of bytes based on first byte */
int utf8_size(char_t c)
{
  if (c >= 192 && c < 224) return 2;
  if (c >= 224 && c < 240) return 3;
  if (c >= 240 && c < 248) return 4;
  return 1; /* if in doubt it is 1 */
}

int prev_utf8_char_size()
{
  int n;
  for (n=2;n<5;n++)
    if (-1 < curbp->b_point - n && (utf8_size(*(ptr(curbp, curbp->b_point - n))) == n))
      return n;
  return 1;
}

void lnend()
{
  char_t *p;
  int cols = 0;
  lnbegin();      // reset the line so we get the right number for `cols`
  while(*(p = ptr(curbp, curbp->b_point)) != '\n' && curbp->b_ebuf > p) {
    ++curbp->b_point;
    cols++;
  }
  /* loop until we get to the correct column */
  while(cols > curwp->w_cols) {
    cols -= curwp->w_cols;
  }
  curbp->b_pcol = cols;      // set it for column-memory
}

void wleft()
{
  char_t *p;
  if ((!isspace(*(p = ptr(curbp, curbp->b_point))) || !is_symbol(*p)) && curbp->b_buf < p)
    --curbp->b_point;
  while ((isspace(*(p = ptr(curbp, curbp->b_point))) || is_symbol(*p)) && curbp->b_buf < p)
    --curbp->b_point;
  while (!isspace(*(p = ptr(curbp, curbp->b_point))) && !is_symbol(*p) && curbp->b_buf < p)
    --curbp->b_point;
  if(isspace(*(p = ptr(curbp, curbp->b_point))) || is_symbol(*p))
    ++curbp->b_point;
}

void wleftdelete()
{
  currentcommand = KBD_DELETE_CHAR;
  iblock();
  wleft();
  copy_cut(TRUE, TRUE, FALSE);
}

void pgdown()
{
  curbp->b_page = curbp->b_point = upup(curbp, curwp, curbp->b_epage);
  while (0 < curbp->b_row--)
    down();
  curbp->b_epage = pos(curbp, curbp->b_ebuf);
  curbp->b_pcol = 0;
}

void pgup()
{
  int i = curwp->w_rows;
  while (0 < --i) {
    curbp->b_page = upup(curbp, curwp, curbp->b_page);
    up();
  }
  curbp->b_pcol = 0;
}

void wright()
{
  char_t *p;
  if ((!isspace(*(p = ptr(curbp, curbp->b_point))) || !is_symbol(*p)) && p < curbp->b_ebuf)
    ++curbp->b_point;
  while ((isspace(*(p = ptr(curbp, curbp->b_point))) || is_symbol(*p)) && p < curbp->b_ebuf)
    ++curbp->b_point;
  while (!isspace(*(p = ptr(curbp, curbp->b_point))) && !is_symbol(*p) && p < curbp->b_ebuf)
    ++curbp->b_point;
}

void wrightdelete()
{
  currentcommand = KBD_DELETE_CHAR;
  iblock();
  wright();
  copy_cut(TRUE, TRUE, FALSE);
}

void insert()
{
  assert(curbp->b_gap <= curbp->b_egap);
  if (curbp->b_gap == curbp->b_egap && !growgap(curbp, CHUNK))
    return;
  curbp->b_point = movegap(curbp, curbp->b_point);
  /* overwrite if mid line, not EOL or EOF, CR will insert as normal */
  if ((curbp->b_flags & B_OVERWRITE) && *input != '\r' && *(ptr(curbp, curbp->b_point)) != '\n' && curbp->b_point < pos(curbp,curbp->b_ebuf) ) {
    *(ptr(curbp, curbp->b_point)) = *input;
    if (curbp->b_point < pos(curbp, curbp->b_ebuf))
      ++curbp->b_point;
  } else {
    *curbp->b_gap++ = *input == '\r' ? '\n' : *input;
    curbp->b_point = pos(curbp, curbp->b_egap);
    // force reframe if scrolled off bottom of screen and at EOF
    if (curbp->b_point == pos(curbp, curbp->b_ebuf) && curbp->b_point >= curbp->b_epage &&
      curwp->w_rows == curwp->w_row)
     curbp->b_reframe = 1;
  }
  curbp->b_flags |= B_MODIFIED;
  undoset_flag = TRUE;
  currentcommand = KBD_INSERT;
}

void insert_str()
{
  int len = strlen((const char *)input);
  assert(curbp->b_gap <= curbp->b_egap);
  undoset(1, FALSE);
  if (curbp->b_gap == curbp->b_egap && !growgap(curbp, CHUNK))
    return;
  curbp->b_point = movegap(curbp, curbp->b_point);
  /* overwrite if mid line, not EOL or EOF, CR will insert as normal */
  if ((curbp->b_flags & B_OVERWRITE) && input[0] != '\r' && *(ptr(curbp, curbp->b_point)) != '\n' && curbp->b_point < pos(curbp,curbp->b_ebuf) ) {
    *(ptr(curbp, curbp->b_point)) = *input;
    if (curbp->b_point < pos(curbp, curbp->b_ebuf))
      ++curbp->b_point;
  } else {
    for(int i = 0; i < len; i++) {
      *curbp->b_gap++ = input[i] == '\r' ? '\n' : input[i];
//      if(input[i] == '\n' || input[i] == '\r')
//        curbp->b_line++;
    }
    curbp->b_point = pos(curbp, curbp->b_egap);
    // force reframe if scrolled off bottom of screen and at EOF
    if (curbp->b_point == pos(curbp, curbp->b_ebuf) && curbp->b_point >= curbp->b_epage &&
        curwp->w_rows == curwp->w_row)
      curbp->b_reframe = 1;
  }
  curbp->b_flags |= B_MODIFIED;
  undoset_flag = TRUE;
}

void insert_unicode()
{
  int len = strlen((const char *)unicode_buf);
  assert(curbp->b_gap <= curbp->b_egap);
  undoset(INSERT, lastcommand == KBD_INSERT);
  if (curbp->b_gap == curbp->b_egap && !growgap(curbp, CHUNK))
    return;
  curbp->b_point = movegap(curbp, curbp->b_point);
  /* overwrite if mid line, not EOL or EOF, CR will insert as normal */
  for(int i = 0; i < len; i++) {
    *curbp->b_gap++ = unicode_buf[i];
  }
  curbp->b_point = pos(curbp, curbp->b_egap);
  // force reframe if scrolled off bottom of screen and at EOF
  if (curbp->b_point == pos(curbp, curbp->b_ebuf) && curbp->b_point >= curbp->b_epage &&
      curwp->w_rows == curwp->w_row)
    curbp->b_reframe = 1;
  curbp->b_flags |= B_MODIFIED;
  undoset_flag = TRUE;
  unicode_buf[0] = '\0';
  currentcommand = KBD_INSERT;
}

void backsp()
{
  undoset(3, lastcommand == KBD_DELETE_CHAR);
  if(curbp->b_point != 0 && *ptr(curbp, curbp->b_point - 1) == '\n')
      curbp->b_line--;
  curbp->b_point = movegap(curbp, curbp->b_point);
  if (curbp->b_buf < curbp->b_gap) {
    curbp->b_gap -= prev_utf8_char_size();
    curbp->b_flags |= B_MODIFIED;
  }
  curbp->b_point = pos(curbp, curbp->b_egap);
  currentcommand = KBD_DELETE_CHAR;
}

void delete()
{
  undoset(2, lastcommand == KBD_DELETE_CHAR);
  curbp->b_point = movegap(curbp, curbp->b_point);
  if (curbp->b_egap < curbp->b_ebuf) {
    curbp->b_egap += utf8_size(*curbp->b_egap);
    curbp->b_point = pos(curbp, curbp->b_egap);
    curbp->b_flags |= B_MODIFIED;
  }
  currentcommand = KBD_DELETE_CHAR;
}

void gotoline()
{
  int line;
  point_t p;
  if (getinput("Goto line: ", temp, STRBUF_S, F_CLEAR, FALSE)) {
    line = atoi(temp);
    p = line_to_point(line);
    if (p != -1) {
      curbp->b_point = p;
      curbp->b_pcol = 0;
      if (curbp->b_epage < pos(curbp, curbp->b_ebuf)) curbp->b_reframe = 1;
      curwp->w_update = TRUE;
      msg("Line %d", line);
    } else {
      msg("Line %d, not found", line);
    }
  }
}

void get_current_path(char *cur_path)
{
  int cutoff = 0;
  for(int i = strlen(curbp->b_fname) - 1; i > -1; i--) {
    if(curbp->b_fname[i] == '/') {
      cutoff = i;
      break;
    }
  }
  for(int i = 0; i <= cutoff; i++)
    cur_path[i] = curbp->b_fname[i];
  cur_path[cutoff+1] = '\0';
}

void insertfile()
{
  char cur_path[NAME_MAX] = "\0";
  if(curbp->b_path) {
    get_current_path(cur_path);
    strcpy(temp, cur_path);
  }
  else
    strcpy(temp, editor_dir);

  if (getfilename("Insert file: ", temp, NAME_MAX))
    (void)insert_file(temp, TRUE);
}

void readfile()
{
  buffer_t *bp;
  char cur_path[NAME_MAX];

  if(curbp->b_path) {
    get_current_path(cur_path);
    strcpy(temp, cur_path);
  }
  else
    strcpy(temp, editor_dir);

  int result = getfilename("Find file: ", (char*)temp, NAME_MAX);

  if (result) {
    bp = find_buffer(temp, TRUE);
    disassociate_b(curwp);
    curbp = bp;
    associate_b2w(curbp, curwp);
    /* load the file if not already loaded */
    if (bp != NULL && bp->b_fname[0] == '\0') {
      if (!load_file(temp)) {
        msg("New file %s", temp);
      }
      strncpy(curbp->b_fname, temp, NAME_MAX);
      curbp->b_fname[NAME_MAX] = '\0'; /* truncate if required */
    }
  }
}

void savebuffer()
{
  const char *message = "No newline at the end of file, add one (Y/n) ?";
  if(curbp->b_flags & B_MODIFIED) {
    /* move the gap to point 0 so that the ebuf is updated. */
    (void) movegap(curbp, 0);
    if(*(curbp->b_ebuf - 1) != '\n') {
      print_to_msgline(message);
      clrtoeol(message, MSGLINE);
      if (yesno(TRUE)) {
        clrtoeol("", MSGLINE);
        *curbp->b_ebuf++ = '\n';
      }
    }
    if (curbp->b_fname[0] != '\0') {
      save(curbp->b_fname);
      return;
    } else {
      writefile();
    }
  } else {
    msg("(No changes need to be saved.)");
  }
}

void writefile()
{
  const char *message = "Write file: ";
  strncpy(temp, curbp->b_fname, NAME_MAX);
  if (getinput((char *)message, temp, NAME_MAX, F_NONE, FALSE))
    if (save(temp) == TRUE)
      strncpy(curbp->b_fname, temp, NAME_MAX);
  clrtoeol(message, MSGLINE);
}

void killbuffer()
{
  buffer_t *kill_bp = curbp;
  buffer_t *bp;
  int bcount = count_buffers();
  const char *message = "Discard changes (y/N) ?";

  /* do nothing if only buffer left is the scratch buffer */
  if (bcount == 1 && 0 == strcmp(get_buffer_name(curbp), "*scratch*"))
    return;

  if (curbp->b_flags & B_MODIFIED) {
    print_to_msgline(message);
    clrtoeol(message, MSGLINE);
    if (!yesno(FALSE))
      return;
  }

  if (bcount == 1) {
    /* create a scratch buffer */
    bp = find_buffer("*scratch*", TRUE);
    strncpy(bp->b_bname, "*scratch*", STRBUF_S);
    bp->b_path = FALSE;
  }

  next_buffer();
  assert(kill_bp != curbp);
  delete_buffer(kill_bp);
}

void iblock()
{
  block();
  msg("Mark set");
}

void unmark()
{
  curbp->b_pmark = curbp->b_mark;
  curbp->b_mark = NOMARK;
  msg("Mark removed");
}

void toggle_overwrite_mode() {
  if (curbp->b_flags & B_OVERWRITE)
    curbp->b_flags &= ~B_OVERWRITE;
  else
    curbp->b_flags |= B_OVERWRITE;
}

void killtoeol()
{
  if (curbp->b_point == pos(curbp, curbp->b_ebuf))
    return; /* do nothing if at end of file */
  if (*(ptr(curbp, curbp->b_point)) == 0xa) {
    delete(); /* delete CR if at start of empty line */
  } else {
    curbp->b_mark = curbp->b_point;
    lnend();
    if (curbp->b_mark != curbp->b_point) copy_cut(TRUE, TRUE, FALSE);
  }
}

void copy_cut(int cut, int displaymsg, int internal)
{
  char_t *p;
  /* if no mark or point == marker, nothing doing */
  if (curbp->b_mark == NOMARK || curbp->b_point == curbp->b_mark)
    return;
  if (scrap != NULL) {
    free(scrap);
    scrap = NULL;
  }
  if(cut && !internal)
    undoset(4, FALSE);
  if (curbp->b_point < curbp->b_mark) {
    /* point above marker: move gap under point, region = marker - point */
    (void) movegap(curbp, curbp->b_point);
    p = ptr(curbp, curbp->b_point);
    nscrap = curbp->b_mark - curbp->b_point;
    if(cut && currentcommand == KBD_DELETE_CHAR)
      for(point_t pt = curbp->b_mark-1; pt > curbp->b_point; pt--) {
        if(*ptr(curbp, pt) == '\n')
          curbp->b_line--;
      }
  } else {
    /* if point below marker: move gap under marker, region = point - marker */
    (void) movegap(curbp, curbp->b_mark);
    p = ptr(curbp, curbp->b_mark);
    nscrap = curbp->b_point - curbp->b_mark;
    if (cut && currentcommand != KBD_DELETE_CHAR)
      for(point_t pt = curbp->b_mark; pt < curbp->b_point; pt++) {
        if(*ptr(curbp, pt) == '\n')
          curbp->b_line--;
      }
  }
  if ((scrap = (char_t*) malloc(nscrap)) == NULL && displaymsg) {
    msg("No more memory available.");
  } else {
    (void) memcpy(scrap, p, nscrap * sizeof (char_t));
    if (cut) {
      curbp->b_egap += nscrap; /* if cut expand gap down */
      curbp->b_point = pos(curbp, curbp->b_egap); /* set point to after region */
      curbp->b_flags |= B_MODIFIED;
      if(displaymsg)
        msg("%ld bytes cut.", nscrap);
      currentcommand = KBD_CUT;
    } else {
      if(displaymsg)
        msg("%ld bytes copied.", nscrap);
    }
    curbp->b_mark = NOMARK;  /* unmark */
  }
}

void paste_internal(int internal)
{
  int new_rows = 0;
  if(curbp->b_flags & B_OVERWRITE)
    return;
  if (nscrap <= 0) {
    msg("Scrap is empty.  Nothing to paste.");
  } else if (nscrap < curbp->b_egap - curbp->b_gap || growgap(curbp, nscrap)) {
    if(!internal)
      undoset(5, FALSE);
    curbp->b_point = movegap(curbp, curbp->b_point);
    memcpy(curbp->b_gap, scrap, nscrap * sizeof (char_t));
    curbp->b_gap += nscrap;
    curbp->b_point = pos(curbp, curbp->b_egap);
    curbp->b_flags |= B_MODIFIED;
    for(int i = 0; scrap[i] != '\0'; i++) {
      if(scrap[i] == '\n')
        new_rows++;
    }
    if (curbp->b_point >= curbp->b_epage)
      curbp->b_reframe = 1;
  }
}

void paste()
{
  paste_internal(FALSE);
}

void showpos()
{
  int current, lastln;
  point_t end_p = pos(curbp, curbp->b_ebuf);

  get_line_stats(&current, &lastln, curbp);

  if (curbp->b_point == end_p) {
    msg("[EOB] Line = %d/%d  Point = %d/%d", current, lastln,
      curbp->b_point, ((curbp->b_ebuf - curbp->b_buf) - (curbp->b_egap - curbp->b_gap)));
  } else {
    char c = unctrl(*(ptr(curbp, curbp->b_point)));
    msg("Char = %c 0x%x  Line = %d/%d  Point = %d/%d", c, *(ptr(curbp, curbp->b_point)),
      current, lastln,
      curbp->b_point, ((curbp->b_ebuf - curbp->b_buf) - (curbp->b_egap - curbp->b_gap)));
  }
}

/* Delete whitespace between non-whitespace */
void deletewhitespacebetween()
{
  char_t *p;
  while (isspace(*(p = ptr(curbp, curbp->b_point - 1))) && curbp->b_buf < p && *p != '\n')
    backsp();
  while (isspace(*(p = ptr(curbp, curbp->b_point))) && curbp->b_buf < p && *p != '\n')
    delete();
}


void insertnewlinebelow()
{
  char_t newline[2];
  newline[0] = '\n';
  newline[1] = '\0';
  input = newline;
  undoset(1, lastcommand == KBD_INSERT);
  insert();
  up();
  currentcommand = KBD_INSERT;
}

void insertnewline()
{
  point_t point;
  char_t *p, *space = NULL, *str;
  int spaces = 0, i;

  point = segstart(curbp, curwp, lnstart(curbp, curbp->b_point), curbp->b_point);
  while(isspace(*(p = ptr(curbp, point))) && *p != '\n' && curwp->w_col != 0) {
    if(spaces == 0) {
      space = p;
    }
    if(*p != '\n') {
      spaces++;
      point++;
    }
  }
  str = (char_t *) malloc(sizeof(char_t)*spaces+2);
  str[0] = '\n';
  for(i = 0; i < spaces; i++) {
    str[i+1] = *space;
  }
  str[i+1] = '\0';
  input = str;
  insert_str();
  curbp->b_pcol = spaces;
  currentcommand = KBD_INSERT;
  free(str);
}

void inserttab()
{
  input = (char_t *)"\t";
  undoset(1, FALSE);
  insert();
}

void inserttabasspace()
{
  input = (char_t *)"  ";
  insert_str();
}

void suspend()
{
  tb_shutdown();
  raise(SIGTSTP);
}

void transpose()
{
  char_t *cur = ptr(curbp, curbp->b_point);
  char_t *prev = ptr(curbp, curbp->b_point-1);
  char_t replace[3];
  point_t mark = curbp->b_mark;
  replace[0] = *cur;
  replace[1] = *prev;
  replace[2] = '\0';
  curbp->b_point--;
  curbp->b_mark = curbp->b_point + 2;
  undoset(REPLACE, 2);
  curbp->b_mark = mark;
  curbp->b_point++;
  memcpy(ptr(curbp, curbp->b_point-1), replace, 2 * sizeof (char_t));
  curbp->b_flags |= B_MODIFIED;
}

/* Delete a word but don't display any messages. */
void deleteword(int dir) {
  block();
  if(dir)
    wright();
  else
    wleft();
  copy_cut(TRUE, FALSE, TRUE);
}

/* Transpose words and put scrap back to how it was. */
void transposeword()
{
  char_t *current_scrap;
  int n_scrap = nscrap;
  point_t mark = curbp->b_mark, epoint, point;

  /* copy the current scrap */
  current_scrap = (char_t*) malloc(nscrap);
  (void) memcpy(current_scrap, scrap, nscrap * sizeof (char_t));

  /* Find all the key points for the undo */
  wright();
  epoint = curbp->b_point;
  wleft();
  wleft();
  curbp->b_mark = epoint;
  point = curbp->b_point;
  undoset(REPLACE, curbp->b_mark - point);

  /* Cut the word to the left*/
  curbp->b_mark = point;
  curbp->b_point = point;
  wright();
  copy_cut(TRUE, FALSE, TRUE);

  /* paste the left word */
  right();
  paste_internal(TRUE);

  /* cut the right word */
  curbp->b_mark = curbp->b_point;
  wright();
  copy_cut(TRUE, FALSE, TRUE);
  wleft();

  /* paste the right word */
  left();
  paste_internal(TRUE);

  /* Put it all back together */
  if (scrap != NULL) {
    free(scrap);
    scrap = NULL;
  }
  nscrap = n_scrap;
  scrap = (char_t*) malloc(nscrap);
  (void) memcpy(scrap, current_scrap, nscrap * sizeof (char_t));
  curbp->b_mark = mark;
}

void lowercaseword()
{
  char_t *p, *word;
  char_t c[2];
  point_t sword, eword;
  int olast = lastcommand;
  while ((isspace(*(p = ptr(curbp, curbp->b_point))) || is_symbol(*p)) && p < curbp->b_ebuf)
    ++curbp->b_point;
  sword = curbp->b_point;
  wright();
  eword = curbp->b_point;
  word = (char_t *) malloc(sizeof(char_t)*(eword - sword));
  curbp->b_point = sword;
  lastcommand = KBD_DELETE_CHAR;
  for(int i = sword, k = 0; i < eword; i++, k++) {
    word[k] = *ptr(curbp, curbp->b_point);
    delete();
  }
  lastcommand = olast;
  for(int i = sword, k = 0; i < eword; i++, k++) {
    c[0] = tolower(word[k]);
    c[1] = '\0';
    input = c;
    undoset(INSERT, i != 0);
    insert();
  }
  free(word);
}

void capitalizeword()
{
  char_t *p;
  while (isspace(*(p = ptr(curbp, curbp->b_point))) && p < curbp->b_ebuf)
    ++curbp->b_point;
  p = ptr(curbp, curbp->b_point);
  char_t c[1];
  c[0] = toupper(*p);
  input = c;
  delete();
  undoset(INSERT, FALSE);
  insert();
  if(isspace(*(p = ptr(curbp, curbp->b_point+1))) || is_symbol(*p))
    curbp->b_point++;
  else
    wright();
}

void uppercaseword()
{
  char_t *p, *word;
  char_t c[2];
  point_t sword, eword;
  int olast = lastcommand;
  while ((isspace(*(p = ptr(curbp, curbp->b_point))) || is_symbol(*p)) && p < curbp->b_ebuf)
    ++curbp->b_point;
  sword = curbp->b_point;
  wright();
  eword = curbp->b_point;
  word = (char_t *) malloc(sizeof(char_t)*(eword - sword));
  curbp->b_point = sword;
  lastcommand = KBD_DELETE_CHAR;
  for(int i = sword, k = 0; i < eword; i++, k++) {
    word[k] = *ptr(curbp, curbp->b_point);
    delete();
  }
  lastcommand = olast;
  for(int i = sword, k = 0; i < eword; i++, k++) {
    c[0] = toupper(word[k]);
    c[1] = '\0';
    input = c;
    undoset(INSERT, i != 0);
    insert();
  }
  free(word);
}

/* type = 0, zap
   type = 1, jump
*/
/* TODO: Throw error when putting non-char in.
*/
void gotochar(int type)
{
  char_t *p;
  point_t opoint = curbp->b_point, eol;
  int c, col = 0;
  struct tb_event ev;
  char *prompt = type == 0 ? "Zap to Char: " : "Jump to Char: ";

  if(character[0] == '\0') {
    display_prompt_and_response(prompt, character);
    tb_present();
    if(tb_poll_event(&ev) != TB_OK) return;
    if(!ev.mod)
      c = ev.ch;
    else
      c = ev.key;

    /* Ignore all control keys other than C-g and ESC*/
    if (c < 32 && c != TB_KEY_CTRL_G && c != TB_KEY_ESC)
      return;
    if(c == TB_KEY_CTRL_G || c == TB_KEY_ESC)
      return;
    else
      character[0] = c;
    display_prompt_and_response(prompt, character);
    tb_present();
  }

  if(type == 0) {
    block();
  }
  if(*ptr(curbp, curbp->b_point) == character[0]) {
    if(negated)
      left();
    else
      right();
  }
  while (*(p = ptr(curbp, curbp->b_point)) != character[0] && p < curbp->b_ebuf && curbp->b_point > 0) {
    if(negated)
      left();
    else
      right();
  }

  if(type == 0 && !negated)
    right();

  if(type == 0)
    copy_cut(TRUE, FALSE, FALSE);

  negated = FALSE;
  tb_set_cursor(0, MSGLINE);
  clrtoeol("", MSGLINE);
  eol = lnstart(curbp, curbp->b_point);
  for(point_t poi = curbp->b_point; poi > eol; poi -= utf8_size(*ptr(curbp,poi)))
    col++;
  curbp->b_pcol = col;
  if(p >= curbp->b_ebuf || curbp->b_point <= 0) {
    msg("No match found.");
    curbp->b_point = opoint;
  }
}

void zaptochar()
{
  gotochar(0);
}

void negated_zaptochar()
{
  negated = TRUE;
  gotochar(0);
}

void jumptochar()
{
  gotochar(1);
}

void negated_jumptochar()
{
  negated = TRUE;
  gotochar(1);
}

void poptomark()
{
  if(curbp->b_mark > -1)
    curbp->b_point = curbp->b_mark;
  else
    curbp->b_point = curbp->b_pmark;
}

void universal_argument_load()
{
  universal_argument++;
  msg("C-u %d", universal_argument);
}

void numeric_argument_load()
{
  numeric_argument = (numeric_argument * 10) + atoi((const char *)&input_char);
  msg("C-u %d", numeric_argument);
}

void back_to_indentation()
{
  char_t *p;
  while (isspace(*(p = ptr(curbp, curbp->b_point))) && p < curbp->b_ebuf)
    ++curbp->b_point;
}

void negate()
{
  negated = !negated;
  msg("C-u -");
}

void forward_bracket()
{
  point_t p, eol;
  int col = 0;
  if((p = find_matching_bracket(curbp, curwp, 1, FALSE)) >= 0)
    curbp->b_point = curbp->b_mark == NOMARK ? p : p + 1;
  /* Make sure the column memory updates to the new column */
  eol = lnstart(curbp, curbp->b_point);
  for(p = curbp->b_point; p > eol; p -= utf8_size(*ptr(curbp,p)))
    col++;
  curbp->b_pcol = col;
}

void backward_bracket()
{
  point_t p, eol;
  int col = 0;
  if((p = find_matching_bracket(curbp, curwp, -1, FALSE)) >= 0) {
    curbp->b_point = p;
    if(curbp->b_mark != NOMARK)
      curbp->b_mark++;
  }
  /* Make sure the column memory updates to the new column */
  eol = lnstart(curbp, curbp->b_point);
  for(p = curbp->b_point; p > eol; p -= utf8_size(*ptr(curbp,p)))
    col++;
  curbp->b_pcol = col;
}

void start_kbd_macro()
{
  record_input = TRUE;
  for(int i = 0; i < record_buffer_index; i++) {
    memset(&record_buffer[i], 0, sizeof(record_buffer[i]));
  }
  record_buffer_index = 0;
  msg("Started keyboard macro...");
}

void end_kbd_macro()
{
  record_input = FALSE;
  msg("Ended keyboard macro.");
}

void run_kbd_macro()
{
  if(numeric_argument > 0)
    numeric_argument--;
  execute_kbd_macro = TRUE;
}

void open_file_from_shell()
{
  get_popen_data(1);
}

void insert_from_shell()
{
  get_popen_data(0);
}
